/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2008 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.gui2.sort;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import edu.umd.cs.findbugs.gui2.BugAspects;
import edu.umd.cs.findbugs.gui2.BugLeafNode;
import edu.umd.cs.findbugs.gui2.BugRenderer;
import edu.umd.cs.findbugs.gui2.BugSelectionListener;
import edu.umd.cs.findbugs.gui2.BugSelector;
import edu.umd.cs.findbugs.gui2.BugTreeModel;
import edu.umd.cs.findbugs.gui2.BugTreePopupProvider;
import edu.umd.cs.findbugs.gui2.Debug;
import edu.umd.cs.findbugs.gui2.Driver;
import edu.umd.cs.findbugs.gui2.IApplicationState;
import edu.umd.cs.findbugs.gui2.SwingThread;

/**
 * @author graham
 */
public class SortableBugTree extends JTree implements BugSelector {

	protected final IApplicationState appState;
	protected final BugTreePopupProvider popupProvider;

	public SortableBugTree(BugTreeModel model, IApplicationState appState, BugTreePopupProvider popupProvider) {
		this.popupProvider = popupProvider.cloneForTree(this);
		this.appState = appState;
		this.setModel(model);

		if (superShouldInitialiseSettings()) {
			initialise();
		}
	}

	protected void initialise() {
		initialiseSettings();
		setupTreeListeners();
	}

	public SortableBugTree(IApplicationState appState, BugTreePopupProvider popupProvider) {
		this(null, appState, popupProvider);
	}

	protected void initialiseSettings() {
		this.setLargeModel(true);
		this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		this.setCellRenderer(bugCellRenderer());
		this.setRowHeight((int) (Driver.getFontSize() + 7));
	}

	protected BugRenderer bugCellRenderer() {
		return new BugRenderer();
	}

	/*
	 * Subclasses should override to allow computation before initialising. For example, assigning
	 * fields. If this returns false, initialise() should be called at the end of the constructor.
	 */
	protected boolean superShouldInitialiseSettings() {
		return true;
	}

	private void setupTreeListeners() {
		addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent selectionEvent) {

				TreePath path = selectionEvent.getNewLeadSelectionPath();
				if (path != null) {
					appState.saveComments();

					Object lastPathComponent = path.getLastPathComponent();
					boolean beforeProjectChanged = appState.projectChanged();
					if (lastPathComponent instanceof BugLeafNode) {
						BugLeafNode currentSelectedBugLeaf = (BugLeafNode) lastPathComponent;
						notifySelectedBugLeaf(currentSelectedBugLeaf);
					} else {
						appState.updateDesignationDisplay();
						BugAspects currentSelectedBugAspects = (BugAspects) lastPathComponent;
						notifyBugAspectsSelected(currentSelectedBugAspects);
					}
					appState.syncBugInformation();
					appState.setProjectChanged(beforeProjectChanged);
				}

				// Debug.println("Tree selection count:" + tree.getSelectionCount());
				if (getSelectionCount() != 1) {
					Debug.println("Tree selection count not equal to 1, disabling comments tab" + selectionEvent);

					appState.setUserCommentInputEnable(false);
					notifyEmptySelection();
				}
			}

		});

		class PopupMouseListener extends MouseAdapter {
			public void mouseClicked(MouseEvent e) {
				TreePath path = getPathForLocation(e.getX(), e.getY());

				if (path == null)
					return;

				if ((e.getButton() == MouseEvent.BUTTON3) || (e.getButton() == MouseEvent.BUTTON1 && e.isControlDown())) {

					if (getModel().isLeaf(path.getLastPathComponent())) {
						setSelectionPath(path);
						popupProvider.showBugPopupMenu(SortableBugTree.this, e.getX(), e.getY());
					} else {
						setSelectionPath(path);
						if (!(path.getParentPath() == null))// If the path's parent path is null,
							// the root was selected, dont allow
							// them to filter out the root.
							popupProvider.showBranchPopupMenu(SortableBugTree.this, e.getX(), e.getY());
					}
				}
			}
		}

		addMouseListener(new PopupMouseListener());
	}

	public SortableBugTree cloneWithNewModel(BugTreeModel newModel) {
		SortableBugTree newTree = new SortableBugTree(newModel, appState, popupProvider);

		for (BugSelectionListener listener : listeners) {
			newTree.addBugSelectionListener(listener);
		}

		return newTree;
	}
	
	public void doSelectionClick() {
		for (TreeSelectionListener treeSelectionListener : getTreeSelectionListeners()) {
			TreePath selectionPath = getSelectionPath();
			treeSelectionListener.valueChanged(new TreeSelectionEvent(treeSelectionListener, selectionPath, false, null, selectionPath));
		}
	}

	private final List<BugSelectionListener> listeners = new ArrayList<BugSelectionListener>();

	public void addBugSelectionListener(BugSelectionListener toAdd) {
		listeners.add(toAdd);
	}

	public void removeBugSelectionListener(BugSelectionListener toRemove) {
		listeners.remove(toRemove);
	}

	private void notifySelectedBugLeaf(BugLeafNode currentSelectedBugLeaf) {
		for (BugSelectionListener listener : listeners) {
			listener.bugLeafSelected(currentSelectedBugLeaf);
		}
	}

	private void notifyBugAspectsSelected(BugAspects currentSelectedBugAspects) {
		for (BugSelectionListener listener : listeners) {
			listener.bugAspectedSelected(currentSelectedBugAspects);
		}
	}

	private void notifyEmptySelection() {
		for (BugSelectionListener listener : listeners) {
			listener.emptySelection();
		}
	}

	public void sort() {
		bugTreeModel().needToRebuild();
		bugTreeModel().checkSorter();
	}

	public BugTreeModel bugTreeModel() {
		BugTreeModel bugTreeModel = (BugTreeModel) getModel();
		return bugTreeModel;
	}
	
	@SwingThread
	public void expandAll() {
		BugAspects root = (BugAspects) bugTreeModel().getRoot();
		expandAll(this, new TreePath(root));
	}

	private void expandAll(SortableBugTree tree, TreePath parent) {
		BugAspects node = (BugAspects) parent.getLastPathComponent();
		for (int i = 0; i < bugTreeModel().getChildCount(node); i++) {
			Object child = bugTreeModel().getChild(node, i);

			if (child instanceof BugAspects) {
				BugAspects childAspects = (BugAspects) child;
				TreePath pathToAspects = parent.pathByAddingChild(childAspects);
				expandAll(this, pathToAspects);
			}
		}

		tree.expandPath(parent);
	}

	@SwingThread
	public boolean leavesShown() {
		int rows = this.getRowCount();
		for (int i = 0; i < rows; i++) {
			TreePath treePath = this.getPathForRow(i);
			Object lastPathComponent = treePath.getLastPathComponent();
			if (lastPathComponent instanceof BugLeafNode)
				return true;
		}
		return false;
	}

	@SwingThread
	public void expandToFirstLeaf(int max) {
		expandToFirstLeaf(max, false);
	}

	private void expandToFirstLeaf(int max, boolean andSelect) {
		if (leavesShown())
			return;
		int i = 0;
		while (true) {
			int rows = this.getRowCount();
			if (i >= rows || rows >= max)
				break;
			TreePath treePath = this.getPathForRow(i);
			Object lastPathComponent = treePath.getLastPathComponent();


			if (lastPathComponent instanceof BugLeafNode) {
				if(andSelect) {
					this.setSelectionRow(i);
				}
				return;
			}
			
			this.expandRow(i++);
		}
	}

	
	@SwingThread
	public void expandToAndSelectFirstLeaf(int max) {
		expandToFirstLeaf(max, true);
	}

	@SwingThread
	public void expandTree(int max) {
		int i = 0;
		while (true) {
			int rows = getRowCount();
			if (i >=  rows || rows >= max)
				break;
			expandRow(i++);
		}		
	}

	@SwingThread
	public void expandTree() {
		expandTree(Integer.MAX_VALUE);
	}

}
